package demoMod.gdxform.abstracts;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import demoMod.gdxform.core.AbstractAction;
import demoMod.gdxform.core.ActionManager;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.interfaces.Border;
import demoMod.gdxform.interfaces.Element;
import demoMod.gdxform.interfaces.ISubscriber;
import demoMod.gdxform.interfaces.ResizeEventSubscriber;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 容器元素，可以存放其他元素
 */
public abstract class Container<T extends Element> implements Element, Border, ResizeEventSubscriber {
    private final List<T> elements = new ArrayList<>();
    protected final ActionManager actionManager = new ActionManager();
    private Container<? extends Element> parent;
    private float x;
    private float y;
    private float width;
    private float height;
    private float borderWidth;
    private Color background = Color.DARK_GRAY;
    private Color border;
    protected Texture bgTexture;
    protected Texture borderTexture;
    private boolean resizable = false;
    private String id = "";
    private boolean visible = true;
    protected FrameBuffer frameBuffer = null;
    protected boolean activated = false;

    protected static final AtomicInteger incr = new AtomicInteger(0);

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void setId(String id) {
        this.id = id;
    }

    public Element getElementById(String id) {
        for (Element element : elements) {
            if (element.getId().equals(id)) {
                return element;
            }
        }
        return null;
    }

    @Override
    public void update() {
        for (Element element : elements) {
            element.update();
        }
        actionManager.update();
    }

    @Override
    public void render(SpriteBatch sb) {
        if (!this.visible) return;
        getFrameBuffer().begin();
        sb.setColor(Color.WHITE);

        if (this.borderTexture != null) {
            sb.draw(this.borderTexture, getX(true) - borderWidth, getY(true) - borderWidth, getWidth() + borderWidth * 2, getHeight() + borderWidth * 2);
        }
        if (this.bgTexture != null) {
            sb.draw(this.bgTexture, getX(true), getY(true), getWidth(), getHeight());
        }
        sb.flush();
        getFrameBuffer().end();

        for (Element element : elements) {
            if (!(element instanceof Container)) {
                getFrameBuffer().begin();
            }
            element.render(sb);
            sb.flush();
            if (!(element instanceof Container)) {
                getFrameBuffer().end();
            }
        }
        Texture texture = getFrameBuffer().getColorBufferTexture();
        sb.setColor(Color.WHITE);
        if (getParent() == null) {
            sb.draw(texture,
                    (int) getX(true) - (int) this.getBorderWidth(), (int) getY(true) - (int) this.getBorderWidth(),
                    0, 0,
                    (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                    1, 1,
                    0,
                    (int) getX(true) - (int) this.getBorderWidth(), (int) getY(true) - (int) this.getBorderWidth(),
                    (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                    false, true); //超出容器宽高范围部分的元素不渲染
        } else {
            getFrameBuffer().begin();
            sb.draw(texture,
                    (int) getParent().getSelfX(true) - (int) getParent().getBorderWidth(), (int) getParent().getSelfY(true) - (int) getParent().getBorderWidth(),
                    0, 0,
                    (int) getParent().getWidth() + (int) getParent().getBorderWidth() * 2, (int) getParent().getHeight() + (int) getParent().getBorderWidth() * 2,
                    1, 1,
                    0,
                    (int) getParent().getSelfX(true) - (int) getParent().getBorderWidth(), (int) getParent().getSelfY(true) - (int) getParent().getBorderWidth(),
                    (int) getParent().getWidth() + (int) getParent().getBorderWidth() * 2, (int) getParent().getHeight() + (int) getParent().getBorderWidth() * 2,
                    false, true); //超出容器宽高范围部分的元素不渲染
            sb.flush();
            getFrameBuffer().end();
        }
        sb.flush();
    }

    public FrameBuffer getFrameBuffer() {
        if (frameBuffer == null) {
            frameBuffer = FormManager.getInstance().getFrameBuffer();
        }
        return frameBuffer;
    }

    protected void setFrameBuffer(FrameBuffer frameBuffer) {
        this.frameBuffer = frameBuffer;
    }

    public void addElement(T element) {
        if (element.getParent() == this) {
            if (element instanceof Container) {
                if (((Container<? extends Element>) element).getElements().contains(this)) {
                    throw new IllegalStateException("Containers cannot be parent to each other.");
                }
            }
        }
        elements.add(element);
        element.setParent(this);
        sortElements();
    }

    public void sortElements() {
        elements.sort(Comparator.comparingInt(ISubscriber::getPriority));
    }

    public void removeElement(Element element) {
        this.actionManager.addToSequenceBot(new AbstractAction.Builder().setActionBody(duration -> {
            elements.remove(element);
            element.setParent(null);
            return true;
        }).build());
    }

    public List<T> getElements() {
        return new ArrayList<>(this.elements);
    }

    protected void clearElements() {
        this.elements.clear();
    }

    @Override
    public Container<? extends Element> getParent() {
        return parent;
    }

    @Override
    public void setParent(Container<? extends Element> parent) {
        this.parent = parent;
    }

    @Override
    public float getX(boolean isAbsolute) {
        return isAbsolute && getParent() != null ? getParent().getX(true) + x : x;
    }

    @Override
    public float getY(boolean isAbsolute) {
        return isAbsolute && getParent() != null ? getParent().getY(true) + y : y;
    }

    public float getSelfX(boolean isAbsolute) {
        return getX(isAbsolute);
    }

    public float getSelfY(boolean isAbsolute) {
        return getY(isAbsolute);
    }

    @Override
    public void setX(float x) {
        this.x = x;
    }

    @Override
    public void setY(float y) {
        this.y = y;
    }

    @Override
    public float getWidth() {
        return this.width;
    }

    @Override
    public float getHeight() {
        return this.height;
    }

    @Override
    public void setWidth(float width) {
        this.width = width;
    }

    @Override
    public void setHeight(float height) {
        this.height = height;
    }

    @Override
    public float getBorderWidth() {
        return borderWidth;
    }

    @Override
    public void setBorderWidth(float borderWidth) {
        this.borderWidth = borderWidth;
    }

    @Override
    public Color getBackground() {
        return background;
    }

    @Override
    public void setBackground(Color background) {
        if (!background.equals(this.background)) {
            this.background = background;
            refreshBackground();
        }
    }

    private void refreshBackground() {
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(this.background);
        pixmap.fill();
        if (this.bgTexture != null) {
            this.bgTexture.dispose();
        }
        this.bgTexture = new Texture(pixmap);
    }

    public void setBackground(Texture texture) {
        if (this.bgTexture != null) {
            this.bgTexture.dispose();
        }
        this.bgTexture = texture;
    }

    @Override
    public Color getBorder() {
        return border;
    }

    @Override
    public void setBorder(Color border) {
        if (this.borderWidth > 0) {
            this.border = border;
            refreshBorder();
        }
    }

    private void refreshBorder() {
        if (this.borderWidth == 0 || this.border == null) return;
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(this.border);
        pixmap.fill();
        if (this.borderTexture != null) {
            this.borderTexture.dispose();
        }
        this.borderTexture = new Texture(pixmap);
    }

    @Override
    public boolean isResizable() {
        return resizable && this.borderWidth > 0;
    }

    @Override
    public void setResizable(boolean resizable) {
        this.resizable = resizable;
    }

    @Override
    public void resize(float deltaX, float deltaY, boolean fromParent) {
        if (this.borderWidth == 0 || fromParent) return;
        float x = Gdx.input.getX();
        float y = Gdx.graphics.getHeight() - Gdx.input.getY();
        float lastX = x - Gdx.input.getDeltaX();
        float lastY = y + Gdx.input.getDeltaY();

        float startX1 = getX(true) - this.borderWidth;
        float endX1 = getX(true);
        float startX2 = getX(true) + getWidth();
        float endX2 = getX(true) + getWidth() + this.borderWidth;

        float startY1 = getY(true) - this.borderWidth;
        float endY1 = getY(true);
        float startY2 = getY(true) + getHeight();
        float endY2 = getY(true) + getHeight() + this.borderWidth;

        if (lastX >= startX1 && lastX <= endX1 && lastY >= startY1 && lastY <= endY2 && getWidth() - deltaX > 0) {
            this.x = x - (getParent() == null ? 0 : getParent().getX(true));
            this.setWidth(getWidth() - deltaX);
        }
        if (lastX >= startX1 && lastX <= endX2 && lastY >= startY2 && lastY <= endY2 && getHeight() - deltaY > 0) {
            this.setHeight(getHeight() - deltaY);
        }
        if (lastX >= startX2 && lastX <= endX2 && lastY >= startY1 && lastY <= endY2 && getWidth() + deltaX > 0) {
            this.setWidth(getWidth() + deltaX);
        }
        if (lastX >= startX1 && lastX <= endX2 && lastY >= startY1 && lastY <= endY1 && getHeight() + deltaY > 0) {
            this.y = y - (getParent() == null ? 0 : getParent().getY(true));
            this.setHeight(getHeight() + deltaY);
        }
    }

    @Override
    public boolean enabled() {
        return true;
    }

    @Override
    public void setEnabled(boolean enabled) {

    }

    @Override
    public void activate() {
        this.activated = true;
    }

    @Override
    public void deactivate() {
        this.activated = false;
    }

    public boolean isActivated() {
        return activated;
    }

    @Override
    public void dispose() {
        for (Element element : elements) {
            element.dispose();
        }
        if (this.bgTexture != null) {
            this.bgTexture.dispose();
        }
        if (this.borderTexture != null) {
            this.borderTexture.dispose();
        }
    }

    @Override
    public boolean visible() {
        return visible;
    }

    @Override
    public void setVisible(boolean visible) {
        this.visible = visible;
    }
}
